package com.csthink.core.business.service.impl;

import com.csthink.common.infrastructure.constant.Constants;
import com.csthink.common.infrastructure.enums.ErrorEnum;
import com.csthink.common.infrastructure.enums.OpSystemConfigKeyEnum;
import com.csthink.common.infrastructure.util.AssertUtil;
import com.csthink.common.infrastructure.util.IDGenerator;
import com.csthink.common.infrastructure.util.SystemConfigLocalCacheUtils;
import com.csthink.core.business.service.OpSystemConfigService;
import com.csthink.mysql.dto.OpSystemConfigDTO;
import com.csthink.mysql.entity.OpSystemConfig;
import com.csthink.mysql.repository.OpSystemConfigRepository;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * @author <a href="mailto:security.2009@live.cn">Mars</a>
 * @since 2023-09-11
 */
@Slf4j
@Service
public class OpSystemConfigServiceImpl implements OpSystemConfigService {

    @Value("${cst.system.config.localCache.enable:false}")
    private boolean enableLocalCache;

    private final AtomicBoolean enableHotKey = new AtomicBoolean(false);

    @Setter(onMethod_ = @Autowired)
    private OpSystemConfigRepository opSystemConfigRepository;

    @Override
    public void initHotKeys() {
        enableHotKey.set(false);
        if (!enableLocalCache) {
            log.warn("local cache is disabled!");
            return;
        }

        OpSystemConfig opSystemConfig = opSystemConfigRepository.getByKey(OpSystemConfigKeyEnum.SYSTEM_CONFIG_HOTKEYS.getKey());
        if (opSystemConfig != null) {
            String value = opSystemConfig.getValue();
            if (StringUtils.isBlank(value)) {
                log.warn("{} is null!", OpSystemConfigKeyEnum.SYSTEM_CONFIG_HOTKEYS.getKey());
                return;
            }
            String[] hotKeys = value.trim().split(",");
            if (hotKeys.length > 0) {
                enableHotKey.set(true);
                SystemConfigLocalCacheUtils.flushKey();
                SystemConfigLocalCacheUtils.putHotKeys(hotKeys);
                SystemConfigLocalCacheUtils.flushAllCache();
            } else {
                enableHotKey.set(false);
            }
        }
    }

    @Override
    public OpSystemConfig get(String key) {
        OpSystemConfig opSystemConfig;
        String value = null;
        if (enableHotKey.get()) {
            value = SystemConfigLocalCacheUtils.getCache(key);
        }
        if (value != null) {
            opSystemConfig = new OpSystemConfig();
            opSystemConfig.setKey(key);
            opSystemConfig.setValue(value);
            return opSystemConfig;
        }
        opSystemConfig = this.query(key);
        if (opSystemConfig != null) {
            if (enableHotKey.get() && SystemConfigLocalCacheUtils.isHotKey(key)) {
                SystemConfigLocalCacheUtils.putCache(key, opSystemConfig.getValue());
            }
            return opSystemConfig;
        } else {
            return null;
        }
    }

    @Override
    public OpSystemConfig get(OpSystemConfigKeyEnum systemConfigKey) {
        String key = systemConfigKey.getKey();
        OpSystemConfig opSystemConfig;
        String value = null;
        if (enableHotKey.get()) {
            value = SystemConfigLocalCacheUtils.getCache(key);
        }
        if (value != null) {
            opSystemConfig = new OpSystemConfig();
            opSystemConfig.setKey(key);
            opSystemConfig.setValue(value);
            return opSystemConfig;
        }
        opSystemConfig = this.query(key);
        if (opSystemConfig != null) {
            if (enableHotKey.get() && SystemConfigLocalCacheUtils.isHotKey(key)) {
                SystemConfigLocalCacheUtils.putCache(key, opSystemConfig.getValue());
            }
            return opSystemConfig;
        } else {
            if (Objects.nonNull(systemConfigKey.getDefaultValue())) {
                opSystemConfig = new OpSystemConfig();
                opSystemConfig.setKey(systemConfigKey.getKey());
                opSystemConfig.setValue(systemConfigKey.getDefaultValue());
                return opSystemConfig;
            } else {
                return null;
            }
        }
    }

    @Override
    public OpSystemConfig query(String key) {
        StopWatch sw = new StopWatch();
        sw.start("opSystemConfigMapper.selectList");
        List<OpSystemConfig> configs = opSystemConfigRepository.listByKey(key);
        sw.stop();
        log.debug("opSystemConfigMapper.selectList cost:{}", sw.getTotalTimeMillis());
        if (CollectionUtils.isNotEmpty(configs)) {
            return configs.get(0);
        }
        return null;
    }

    @Override
    public void set(OpSystemConfigDTO configDTO) {
        OpSystemConfig config = this.get(configDTO.getKey());
        if (config == null) {
            config = new OpSystemConfig();
            config.setId(IDGenerator.getId());
            config.setKey(configDTO.getKey());
            config.setValue(configDTO.getValue());
            config.setCreateTime(new Date());
            config.setCreateBy(configDTO.getOpUser());
            config.setModifyTime(new Date());
            config.setModifyBy(configDTO.getOpUser());
            config.setComment(configDTO.getComment());
            opSystemConfigRepository.save(config);
        } else {
            config.setValue(configDTO.getValue());
            config.setModifyTime(new Date());
            config.setModifyBy(configDTO.getOpUser());
            config.setComment(configDTO.getComment());
            opSystemConfigRepository.updateById(config);
        }
        if (enableHotKey.get() && SystemConfigLocalCacheUtils.isHotKey(configDTO.getKey())) {
            this.sendHotKeyUpdateMessage(
                    Constants.SYSTEM_CONFIG_LOCAL_CACHE_UPDATE, configDTO.getKey());
        }
        if (enableLocalCache
                && StringUtils.equalsIgnoreCase(
                configDTO.getKey(), OpSystemConfigKeyEnum.SYSTEM_CONFIG_HOTKEYS.getKey())) {
            this.flushLocalCacheKey();
        }
    }

    @Override
    public void setNoAuth(String key, String value) {
        OpSystemConfig config = this.get(key);
        String comment = config == null ? "" : config.getComment();
        OpSystemConfigDTO configDTO = new OpSystemConfigDTO();
        configDTO.setKey(key);
        configDTO.setValue(value);
        configDTO.setOpUser("System");
        configDTO.setComment(comment);
        this.set(configDTO);
    }

    @Override
    public List<OpSystemConfig> list(String key) {
        return opSystemConfigRepository.listByKey(key);
    }

    @Override
    public void delete(String key) {
        OpSystemConfig opSystemConfig = this.get(key);
        AssertUtil.notNull(opSystemConfig, ErrorEnum.OP_SYSTEM_CONFIG_KEY_NOT_EXIST);
        opSystemConfigRepository.removeById(opSystemConfig.getId());

        if (enableHotKey.get() && SystemConfigLocalCacheUtils.isHotKey(key)) {
            this.sendHotKeyUpdateMessage(Constants.SYSTEM_CONFIG_LOCAL_CACHE_UPDATE, key);
        }
        if (enableHotKey.get()
                && StringUtils.equalsIgnoreCase(
                key, OpSystemConfigKeyEnum.SYSTEM_CONFIG_HOTKEYS.getKey())) {
            this.flushLocalCacheKey();
        }
    }

    @Override
    public boolean isEnableSystemFeature(OpSystemConfigKeyEnum systemConfigKey) {
        try {
            OpSystemConfig opSystemConfig = this.get(systemConfigKey);
            if (opSystemConfig == null) {
                return false;
            }
            String value = opSystemConfig.getValue();
            if (StringUtils.isEmpty(value)) {
                return false;
            }
            return BooleanUtils.toBoolean(value);
        } catch (Exception e) {
            log.warn("isEnableSystemFeature error", e);
            return false;
        }
    }

    @Override
    public boolean isDisableSystemFeature(OpSystemConfigKeyEnum systemConfigKey) {
        return !isEnableSystemFeature(systemConfigKey);
    }

    @Override
    public void sendHotKeyUpdateMessage(String type, String data) {
        if (StringUtils.isBlank(type)) {
            log.warn("type is null!");
            return;
        }
//        log.info(
//                "send hotKey update message, channel: {}, type:{}, data: {}.",
//                systemConfigHotKeyUpdateChannel,
//                type,
//                data);
//        SystemConfigUpdateMessage systemConfigUpdateMessage = new SystemConfigUpdateMessage();
//        systemConfigUpdateMessage.setType(type);
//        systemConfigUpdateMessage.setData(data);
//        systemConfigLocalCacheProducer.produce(systemConfigUpdateMessage);
    }

    @Override
    public void flushAllLocalCache() {
        if (enableHotKey.get()) {
            log.info("flush all local cache!");
            sendHotKeyUpdateMessage(Constants.SYSTEM_CONFIG_LOCAL_CACHE_FLUSH_ALL, "");
        }
    }

    @Override
    public void flushLocalCacheKey() {
        if (enableLocalCache) {
            // todo need to be upgrade
            log.info("flush all local cache key!");
            sendHotKeyUpdateMessage(Constants.SYSTEM_CONFIG_LOCAL_CACHE_KEYS_UPDATE, "");
        }
    }

    @Override
    public boolean isAccountEnableFeature(String key, String accountId) {
        OpSystemConfig opSystemConfig = this.get(key);
        Optional<String[]> enableAccountId =
                Optional.ofNullable(opSystemConfig)
                        .map(OpSystemConfig::getValue)
                        .map(value -> value.split(","));
        if (enableAccountId.isEmpty()) {
            return false;
        }

        List<String> enableAccountIdList = Arrays.asList(enableAccountId.get());
        if (enableAccountIdList.contains("all")) {
            return true;
        }

        return enableAccountIdList.contains(accountId);
    }

    @Override
    public boolean isAccountDisableFeature(String key, String accountId) {
        OpSystemConfig opSystemConfig = this.get(key);
        Optional<String[]> disableAccountId =
                Optional.ofNullable(opSystemConfig)
                        .map(OpSystemConfig::getValue)
                        .map(value -> value.split(","));
        if (disableAccountId.isEmpty()) {
            return false;
        }

        List<String> disableAccountIdList = Arrays.asList(disableAccountId.get());
        if (disableAccountIdList.contains("none")) {
            return false;
        }

        return disableAccountIdList.contains(accountId);
    }

    @Override
    public Map<String, Boolean> listFeatures(String accountId, Set<OpSystemConfigKeyEnum> keys) {
        Map<String, Boolean> result = new HashMap<>();
        if (StringUtils.isBlank(accountId)) {
            return result;
        }
        if (CollectionUtils.isNotEmpty(keys)) {
            Set<String> featureKeys =
                    keys.stream().map(OpSystemConfigKeyEnum::getKey).collect(Collectors.toSet());
            List<OpSystemConfig> features = opSystemConfigRepository.listByKeys(featureKeys);
            result = featureWrapper(accountId, features);
            // if key is not config, disable it.
            for (String key : featureKeys) {
                if (!result.containsKey(key)) {
                    result.put(key, false);
                }
            }
        }
        return result;
    }


    @Override
    public List<OpSystemConfig> list(Set<OpSystemConfigKeyEnum> keys) {
        if (CollectionUtils.isNotEmpty(keys)) {
            Set<String> featureKeys =
                    keys.stream().map(OpSystemConfigKeyEnum::getKey).collect(Collectors.toSet());
            return opSystemConfigRepository.listByKeys(featureKeys);
        }
        return null;
    }

    private Map<String, Boolean> featureWrapper(String accountId, List<OpSystemConfig> list) {
        Map<String, Boolean> result = new HashMap<>();
        if (CollectionUtils.isNotEmpty(list)) {
            list.forEach(
                    item -> {
                        String feature = item.getKey();
                        String accountIdString = item.getValue();
                        if (StringUtils.equalsIgnoreCase("all", accountIdString)) {
                            result.put(feature, true);
                        } else if (StringUtils.isNotBlank(accountIdString)) {
                            String[] accountIds = StringUtils.split(accountIdString, ",");
                            if (ArrayUtils.isNotEmpty(accountIds)) {
                                for (String aid : accountIds) {
                                    if (StringUtils.equals(aid, accountId)) {
                                        result.put(feature, true);
                                        break;
                                    }
                                }
                            }
                        } else {
                            result.put(feature, false);
                        }
                    });
        }
        return result;
    }
}
